﻿define('framework/directive/ControlValidatorServiceConstructor', [
        'angular',
        'jquery',
        'underscore',
        'framework/directive/ControlValidator',
        'config.properties'
    ], function (angular, $, _, ControlValidator, config) {
        var ctx = config.$paths.$current.ctx;
        return function (Arrays, Predicates, $http, $timeout, $q, IllegalMessages, ValidationHolder) {
            var prototypeRef = ControlValidator.prototype;

            var CONTROL_RULE_DEFERREDS = {
                _cache: {},
                add: function (formName, modelControl, rule, deferred){
                    var cache = this._cache;
                    if (!cache[formName]){
                        cache[formName] = {};
                    }
                    if (!cache[formName][modelControl]){
                        cache[formName][modelControl] = {};
                    }
                    if (!cache[formName][modelControl][rule]){
                        cache[formName][modelControl][rule] = [];
                    }
                    cache[formName][modelControl][rule].push(deferred);
                },
                removeDeferreds: function (formName, modelControl, rule) {
                    var cache = this._cache;
                    cache[formName][modelControl][rule] = [];
                },
                getDeferreds: function (formName, modelControl, rule) {
                    var cache = this._cache,
                        result;
                    if (cache[formName] && cache[formName][modelControl])
                        result = cache[formName][modelControl][rule];
                    return result;
                }
            };

            prototypeRef.fillListenControls = function () {
                var me = this,
                    listenProperties = me.getListenProperties(),
                    listenControls = me.listenControls = [];

                if (me.isCombineRule()) {
                    angular.forEach(listenProperties, function (prop) {
                        Arrays.pushAll(me.getCombineMatchedControls(prop), listenControls);
                    });
                } else {
                    me.listenControls = Arrays.transform(listenProperties, function (prop) {
                        return me.form[prop];
                    });
                }
                return me;
            };

            prototypeRef.fillListenControlsTargets$ = function () {
                var me = this;
                angular.forEach(me.listenControls, function (listenControl) {
                    if (listenControl) {
                        me.fillListenControlTargets$(listenControl);
                    }
                });
                return me;
            };

            /**
             * @private
             */
            prototypeRef.fillListenControlTargets$ = function (listenControl) {
                var me = this,
                    $form = me.$form,
                    controlName = listenControl.$name,
                    rule = me.rule,
                    $listenPropTargetCache = me.$listenPropTargetCache,
                    $outerContainerCache = me.$outerContainerCache,
                    $verifyTargetCache = me.$verifyTargetCache,
                    msgControl, $msgTarget;
                if (me.isOverrideListens() && rule.property) {
                    msgControl = me.getSameCombineControl(listenControl, rule.property);
                    $msgTarget = $listenPropTargetCache[controlName] = $form.find('[name=\'' + msgControl.$name + '\']')
                } else {
                    $msgTarget = $listenPropTargetCache[controlName] = $form.find('[name=\'' + controlName + '\']')
                }
                // 默认与 $target 一致
                $outerContainerCache[controlName] = $verifyTargetCache[controlName] = $msgTarget;
                if ($msgTarget.is('[outer-container]')) {
                    $outerContainerCache[controlName] = $msgTarget.parent().closest('[outer-container]');
                }
                if ($msgTarget.is('[verify-target]')) {
                    $verifyTargetCache[controlName] = $msgTarget.find('[verify-target]');
                }
            };

            prototypeRef.isOverrideListens = function () {
                return this.rule.listenProperties;
            };

            prototypeRef.getListenProperties = function () {
                var me = this,
                    rule = me.rule;
                if (me.isOverrideListens()) {
                    return rule.listenProperties;
                } else if (rule.property) {
                    return [rule.property];
                } else if (rule.properties) {
                    return rule.properties;
                }
                return [];
            };

            prototypeRef.getMsgTargetProperties = function () {
                var rule = this.rule;
                if (rule.property) {
                    return [rule.property];
                } else if (rule.properties) {
                    return rule.properties;
                }
                return [];
            };

            prototypeRef.isCombineRule = function () {
                return this.rule.combineName;
            };

            prototypeRef.getCombineMatchedControls = function (prop) {
                var me = this,
                    rule = me.rule,
                    combineName = rule.combineName,
                    form = me.form,
                    matchedControls = [],
                    reg;
                if (prop) {
                    reg = new RegExp('^' + combineName + '(\\\[\\\d+\\\])?\.' + prop + '$');
                    angular.forEach(form, function (control, fieldName) {
                        if (!(fieldName === 'verify' || /^\$/.test(fieldName)) && reg.test(fieldName)) {
                            matchedControls.push(control);
                        }
                    })
                }
                return matchedControls;
            };

            prototypeRef.apply = function () {
                var me = this,
                    gValidator = me.gValidator,
                    currentGroupVerifyFns = gValidator.getCurrentGroupVerifyFns(),
                    listenControls = me.listenControls,
                    rule = me.rule,
                    ruleName = me.ruleName,
                    formName = me.form.$name;
                if (Arrays.isNotEmptyArray(listenControls)) {
                    angular.forEach(listenControls, function (modelControl) {
                        if (modelControl) {
                            var rulePredicate = me[ruleName],
                                $name = modelControl.$name,
                                verifyFn = function (modelValue, sync) {
                                    var isValid;
                                    if (angular.isFunction(rulePredicate) && gValidator.disabled !== true) {
                                        isValid = rulePredicate.call(me, modelValue, modelControl, sync);
                                        if (rule.async !== true) {
                                            me.setValidityAndSendMessage(isValid, modelControl);
                                        }
                                    }
                                    return modelValue;
                                };

                            verifyFn.$modelControl = modelControl;
                            verifyFn.formName = formName;
                            verifyFn.ruleName = ruleName;
                            currentGroupVerifyFns.push(verifyFn);

                            if (!angular.isFunction(modelControl.verify)) {
                                var validFn = function (modelValue) {
                                    $timeout(function(){
                                        _.each(gValidator.getCurrentGroupVerifyFns(), function (fn) {
                                            if (fn.$modelControl === modelControl) {
                                                fn(modelValue);
                                            }
                                        });
                                    });
                                    return modelValue;
                                };
                                modelControl.verify = function (sync) {
                                    var rulePromises = [];
                                    _.each(gValidator.getCurrentGroupVerifyFns(), function (fn) {
                                        if (fn.$modelControl === modelControl) {
                                            var rulePromise = $q.defer();
                                            rulePromises.push(rulePromise.promise);
                                            CONTROL_RULE_DEFERREDS.add(fn.formName, fn.$modelControl.$name, fn.ruleName, rulePromise);
                                            fn(modelControl.$viewValue, sync);
                                        }
                                    });
                                    return $q.all(rulePromises);
                                };
                                me.$verifyTargetCache[$name].bind('blur', modelControl.verify);
                                modelControl.$parsers.unshift(validFn);
                                modelControl.$formatters.unshift(validFn);
                            }
                        }
                    });
                }
                return me;
            };

            function isDeferredLike(deferred) {
                return deferred && angular.isFunction(deferred.resolve) && angular.isFunction(deferred.reject) && angular.isFunction(deferred.notify);
            }

            prototypeRef.setValidityAndSendMessage = function (isValid, modelControl) {
                var me = this,
                    ruleName = me.ruleName,
                    rule = me.rule,
                    formName = me.form.$name,
                    deferreds = CONTROL_RULE_DEFERREDS.getDeferreds(formName, modelControl.$name, ruleName),
                    reason, listenProperties, sameCombineListenControls;
                if (rule.property) {
                    modelControl.$setValidity(ruleName, isValid);
                    me.sendMessage(isValid, modelControl);
                } else {
                    listenProperties = me.getListenProperties();
                    sameCombineListenControls = me.getSameCombineControls(modelControl, listenProperties);
                    angular.forEach(sameCombineListenControls, function (control) {
                        control.$setValidity(ruleName, isValid);
                        me.sendMessage(isValid, control);
                    });
                }
                if (!_.isEmpty(deferreds)) {
                    reason = {isValid: isValid, modelControl: modelControl};
                    _.each(deferreds, function (deferred) {
                        if (isValid) {
                            deferred.resolve(reason);
                        } else {
                            deferred.reject(reason);
                        }
                    });
                    CONTROL_RULE_DEFERREDS.removeDeferreds(formName, modelControl.$name, ruleName);
                }
            };

            /**
             * @deprecated
             */
            prototypeRef.sendIllegalMessages = function (isValid, modelControl) {
                var me = this,
                    rule = me.rule,
                    listenProperties, sameCombineListenControls;
                if (rule.property) {
                    me.sendMessage(isValid, modelControl);
                } else {
                    listenProperties = me.getListenProperties();
                    sameCombineListenControls = me.getSameCombineControls(modelControl, listenProperties);
                    angular.forEach(sameCombineListenControls, function (control) {
                        me.sendMessage(isValid, control);
                    });
                }
            };

            prototypeRef.sendMessage = function (isValid, modelController) {
                var me = this,
                    formName = me.form ? me.form.$name : '',
                    rule = me.rule,
                    ruleName = rule.ruleName,
                    messageKey = rule.message,
                    gValidator = me.gValidator,
                    $outerContainer = me.$outerContainerCache[modelController.$name],
                    $verifyTarget = me.$verifyTargetCache[modelController.$name];
                ValidationHolder.loadI18ns(gValidator.$currentGroupName, function (i18ns) {
                    IllegalMessages.send({
                        isValid: isValid,
                        $outerContainer: $outerContainer,
                        $verifyTarget: $verifyTarget,
                        message: i18ns[messageKey] || '',
                        ruleName: ruleName,
                        formName: formName,
                        messengerName: $outerContainer.data('invalid-msg') || gValidator.defaultIllegalTarget
                    });
                });
            };

            prototypeRef.clearMessages = function () {
                var defaultMessengerName = this.gValidator.defaultIllegalTarget;

                angular.forEach(this.$outerContainerCache, function ($target) {
                    var messengerName = $target.data('invalid-msg') || defaultMessengerName;
                    IllegalMessages.clear(messengerName, $target);
                });
            };

            /**
             * 设置当前规则正在监听的控件的 angular 效验状态
             * @deprecated
             */
            prototypeRef.setListenControlsValidity = function (isValid, modelControl) {
                var me = this,
                    ruleName = me.ruleName,
                    rule = me.rule,
                    listenProperties, sameCombineListenControls;
                if (rule.property) {
                    modelControl.$setValidity(ruleName, isValid);
                } else {
                    listenProperties = me.getListenProperties();
                    sameCombineListenControls = me.getSameCombineControls(modelControl, listenProperties);
                    angular.forEach(sameCombineListenControls, function (control) {
                        control.$setValidity(ruleName, isValid);
                    });
                }
            };

            prototypeRef.notNull = function (rawValue) {
                return angular.isDefined(rawValue) && rawValue !== null;
            };

            prototypeRef.notEmpty = function (rawValue) {
                if (this.notNull(rawValue)) {
                    if (angular.isString(rawValue) || angular.isArray(rawValue)) {
                        return rawValue.length > 0;
                    } else {
                        throw new TypeError('NotEmpty 只支持 String 或 Array 类型的值的验证');
                    }
                }
                return false;
            };

            prototypeRef.notBlank = function (rawValue) {
                return !rawValue || (angular.isString(rawValue) && $.trim(rawValue).length > 0);
            };

            prototypeRef.require = function (rawValue) {
                if (angular.isString(rawValue)) {
                    return rawValue.length > 0;
                } else {
                    return this.notNull(rawValue);
                }
            };
            prototypeRef.numberRange = function (rawValue) {
                return this.range(rawValue);
            };
            prototypeRef.pattern = function (rawValue) {
                var rule = this.rule;

                return !rawValue || (getReg(rule.pattern, rule.flags).test(rawValue));

                function getReg(pattern, flags) {
                    if (flags) {
                        return new RegExp(pattern, flags);
                    } else {
                        return new RegExp(pattern);
                    }
                }
            };

            prototypeRef.min = function (rawValue) {
                var value = Number(rawValue);
                return this.isEmptyString(rawValue) || (angular.isNumber(value) && value >= this.rule.min);
            };

            prototypeRef.max = function (rawValue) {
                var value = Number(rawValue);
                return this.isEmptyString(rawValue) || (angular.isNumber(value) && value <= this.rule.max);
            };

            prototypeRef.length = function (rawValue) {
                return angular.isUndefined(rawValue) || rawValue == null || (angular.isString(rawValue) && rawValue.length <= this.rule.max && rawValue.length >= this.rule.min);
            };

            function getRichLength(value,chineseWidth){
                var len = 0,
                    _chineseWidth = config.controls.validation.chineseWidth;
                if(angular.isUndefined(chineseWidth) || !angular.isNumber(chineseWidth)){
                    if(_chineseWidth){
                        chineseWidth = _chineseWidth;
                    }else{
                        chineseWidth = 3;
                    }
                }
                for(var i = 0;i < value.length; i++){
                    if(value.charCodeAt(i) > 255) //如果是汉字
                        len += chineseWidth;
                    else
                        len++;
                }
                return len;
            }

            prototypeRef.richLength = function(rawValue){
                var value;
                if(!angular.isString(rawValue)){
                    return true;
                }
                value =  getRichLength(rawValue,this.rule.chineseWidth);
                return this.isEmptyString(rawValue) || (angular.isNumber(value) && value >= this.rule.min && value <= this.rule.max);
            };

            prototypeRef.numeric = function (rawValue) {
                var value, token;
                if (rawValue) {
                    value = Number(rawValue);
                    token = rawValue.split('.');

                    if (this.rule.precision >= 0 && (token[1] || '').length > this.rule.precision) {
                        return false;
                    } else if (this.rule.sign === 'POSITIVE' && value < 0) {
                        return false;
                    } else if (this.rule.sign === 'NEGATIVE' && value > 0) {
                        return false;
                    }
                }
                return true;
            };

            prototypeRef.range = function (rawValue) {
                var value = Number(rawValue);
                return this.isEmptyString(rawValue) || (angular.isNumber(value) && value >= this.rule.min && value <= this.rule.max);
            };

            prototypeRef.email = function (rawValue) {
                if (!rawValue) {
                    return true;
                }

                var emailParts = rawValue.toString().split("@");
                if (emailParts.length != 2) {
                    return false;
                }

                if (/\.$/.test(emailParts[0]) || /\.$/.test(emailParts[1])) {
                    return false;
                }

                if (!/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*/i.test(emailParts[0])) {
                    return false;
                }

                return /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)+|\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]/i.test(emailParts[1]);
            };

            /**
             *
             * @param rawValue {Date}
             */
            prototypeRef.dateMax = function (rawValue) {
                if (rawValue == null || !angular.isDate(rawValue)) return true;
                var me = this,
                    rule = me.rule,
                    compareTime = rule.compareTime || new Date().getTime(),
                    shiftedTime = rule.shiftedTime;
                return (compareTime + shiftedTime) >= rawValue.getTime();
            };

            /**
             *
             * @param rawValue {Date}
             */
            prototypeRef.dateMin = function (rawValue) {
                if (rawValue == null || !angular.isDate(rawValue)) return true;
                var me = this,
                    rule = me.rule,
                    compareTime = rule.compareTime || new Date().getTime(),
                    shiftedTime = rule.shiftedTime;
                return (compareTime + shiftedTime) <= rawValue.getTime();
            };

            prototypeRef.allMatch = function (rawValue, modelControl) {
                var me = this,
                    valMap = {},
                    allMatchControls = me.getSameCombineControls(modelControl, me.rule.listenProperties).concat(modelControl),
                    i = 0, modelValue, key;
                angular.forEach(allMatchControls, function (control) {
                    key = control.$viewValue;
                    if (key == null || angular.isUndefined(key)) key = '';
                    valMap[key] = undefined;
                });
                for (modelValue in valMap) {
                    if (valMap.hasOwnProperty(modelValue)) {
                        i++;
                    }
                }
                return i <= 1;
            };

            prototypeRef.compare = function (rawValue, modelControl) {
                var me = this,
                    rule = me.rule,
                    than = rule.than,
                // compare 需要值的具体类型， 不适合使用 $viewValue
                    propertyCtrl = me.getSameCombineControl(modelControl, rule.property),
                    compareToCtrl = me.getSameCombineControl(modelControl, rule.compareTo),
                    propertyValue = propertyCtrl.$viewValue,
                    compareToVal = compareToCtrl.$viewValue;
                if (angular.isDate(propertyValue)) {
                    propertyValue = propertyCtrl.$modelValue;
                }
                if (angular.isDate(compareToVal)) {
                    compareToVal = compareToCtrl.$modelValue;
                }
                if (angular.isUndefined(propertyValue) || angular.isUndefined(compareToVal)
                    || propertyValue === '' || compareToVal === ''
                    || propertyValue === null || compareToVal === null) {
                    return true;
                }
                if (!angular.isDate(propertyValue) && !angular.isDate(compareToVal)) {
                    propertyValue = Number.isNaN(Number(propertyValue)) ? propertyValue : Number(propertyValue);
                    compareToVal = Number.isNaN(Number(compareToVal)) ? compareToVal : Number(compareToVal);
                }
                switch (than) {
                    case 'GREATER':
                        return propertyValue > compareToVal;
                    case 'GREATER_EQ':
                        return propertyValue >= compareToVal;
                    case 'LESS':
                        return propertyValue < compareToVal;
                    case 'LESS_EQ':
                        return propertyValue <= compareToVal;
                    default:
                        return true;
                }
            };

            prototypeRef.unique = function (value, modelControl, sync) {
                var me = this,
                    rule = me.rule,
                    entity = rule.entity,
                    pkProp = rule.pkProperty,
                    properties = rule.properties,
                    existsControls = me.getSameCombineControls(modelControl, properties),
                    fieldNames = [], fieldValues = [],
                    pkControl,
                    pkValue = '',
                    i, flag = false;
                angular.forEach(existsControls, function (v) {
                    fieldNames.push(v.$name);
                    fieldValues.push((angular.isDefined(v.$viewValue) && v.$viewValue != null && !Number.isNaN(v.$viewValue)) ? v.$viewValue : v.$modelValue || '');
                });
                pkControl = me.getSameCombineControl(modelControl, pkProp);
                if (pkControl && angular.isDefined(pkControl.$viewValue) && pkControl.$viewValue != null) {
                    pkValue = pkControl.$viewValue;
                }

                for (i = 0; i < fieldValues.length; i++) {
                    if (fieldValues[i] !== '') {
                        flag = true;
                        break;
                    }
                }
                if (flag) {
                    var jsonKey = JSON.stringify({
                            entityName: entity,
                            pkValue: pkValue,
                            fieldNames: fieldNames,
                            fieldValues: fieldValues
                        }),
                        url = ctx + '/validation/unique';
                    if (typeof sync === 'boolean' && sync) {
                        $.ajax({
                            type: "POST",
                            url: url,
                            failHandler: false,
                            async: false,
                            contentType: 'application/json',
                            data: jsonKey
                        }).success(function (result) {
                            var isValid = result && result.success === true;
                            angular.forEach(existsControls, function (v) {
                                me.setValidityAndSendMessage(isValid, v);
                            });
                            me.reValidTimer = undefined;
                        });
                        return;
                    }

                    if (me.reValidTimer) {
                        $timeout.cancel(me.reValidTimer);
                        me.reValidTimer = undefined;
                    }
                    me.reValidTimer = $timeout(function () {
                        $http.post(url, jsonKey, {failHandler: false}).success(function (result) {
                            var isValid = result && result.success === true;
                            angular.forEach(existsControls, function (v) {
                                me.setValidityAndSendMessage(isValid, v);
                            });
                            me.reValidTimer = undefined;

                            if (!isValid) return;
                            //添加表格内唯一性校验
                            var dataSource = me.$verifyTargetCache[modelControl.$name] && me.$verifyTargetCache[modelControl.$name].data('$dataSource'),
                                arr = 0;
                            if (dataSource) {
                                arr = _.filter(dataSource, function(row){
                                    var j = 0, len = fieldNames.length, fieldName, index, compare;
                                    for (j=0; j<len; j++) {
                                        fieldName = fieldNames[j];
                                        index = fieldName.lastIndexOf(".");
                                        if (index > -1) {
                                            fieldName = fieldName.substring(index + 1);
                                        }
                                        if (angular.isDefined(row[fieldName]) && row[fieldName] != null && !Number.isNaN(row[fieldName])){
                                            compare = row[fieldName];
                                        } else {
                                            compare = '';
                                        }
                                        if (compare != fieldValues[j]) {
                                            return false;
                                        }
                                    }
                                    return true;
                                });
                                if (arr.length > 1) {
                                    angular.forEach(existsControls, function (v) {
                                        me.setValidityAndSendMessage(false, v);
                                    });
                                }
                            }
                        });
                    }, 300);
                } else {
                    me.setValidityAndSendMessage(true, modelControl);
                }
            };

            prototypeRef.eitherNotBlank = function (value, modelControl) {
                var me = this,
                    properties = me.properties,
                    controls = me.getSameCombineControls(modelControl, properties);
                return Arrays.exists(controls, Predicates.newPropPredicate('$viewValue', function (val) {
                    return angular.isString(val) && $.trim(val).length > 0;
                }));
            };

            prototypeRef.dateSpan = function (rawValue, modelControl) {
                var me = this,
                    rule = me.rule,
                    limit = rule.limit,
                    minDateField = me.getSameCombineControl(modelControl, rule.minDateField),
                    maxDateField = me.getSameCombineControl(modelControl, rule.maxDateField),
                    minDate, maxDate, span;
                if (minDateField && maxDateField) {
                    minDate = minDateField.$viewValue;
                    maxDate = maxDateField.$viewValue;
                    if (minDate && maxDate) {
                        span = maxDate.getTime() - minDate.getTime();
                        return span >= 0 && span < limit;
                    }
                }
                return true;
            };

            prototypeRef.isEmptyString = function (value) {
                return !this.notNull(value) || (angular.isString(value) && value.length == 0);
            };

            prototypeRef.getSameCombineControls = function (modelControl, properties) {
                var me = this,
                    sameCombineControls = [];
                angular.forEach(properties, function (prop) {
                    if (prop) {
                        var sameCombineControl = me.getSameCombineControl(modelControl, prop);
                        if (sameCombineControl) {
                            sameCombineControls.push(sameCombineControl)
                        }
                    }
                });
                return sameCombineControls;
            };

            prototypeRef.getSameCombineControl = function (modelControl, property) {
                var me = this,
                    rule = me.rule,
                    form = me.form,
                    controlName = modelControl.$name;
                if (rule.combineName) {
                    var matched = controlName.match(/\[\d]/);
                    if (Arrays.isNotEmptyArray(matched)) {
                        return form[rule.combineName + matched[0] + '.' + property];
                    }
                } else {
                    return form[property];
                }
            };

            return {
                /**
                 *
                 * @param gValidator {FormValidatorController}
                 * @return 控件效验器
                 */
                from: function (gValidator) {
                    return new ControlValidator(gValidator);
                },

                isSupported: function (ruleName) {
                    return angular.isFunction(ControlValidator.prototype[ruleName]);
                }
            }
        };
    }
);